package it.unibo.refolding.pico.stripchart;

import it.unibo.refolding.common.CommonThings;
import it.unibo.refolding.common.Sleeper;
import it.unibo.refolding.pico.CommonClRefoldingOptions;
import it.unibo.refolding.pico.ContactPointAndSlopeHelper;
import it.unibo.refolding.pico.FileMovingWatcher.FileContentAndTargetFile;
import it.unibo.refolding.pico.PeakSlopesHelper;
import it.unibo.refolding.pico.PicoUtil;
import it.unibo.refolding.pico.Wlc;
import it.unimi.dsi.fastutil.doubles.DoubleList;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.Parser;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Function;
import com.google.common.collect.Sets;

public class AnalyseRefolding {

  @SuppressWarnings("unused")
  private static final Logger logger = Logger.getLogger(AnalyseRefolding.class.getSimpleName());

  /**
   * params should mainly be empty because the junction points are computed by
   * FetchRefoldBreak.
   * 
   * @throws InterruptedException
   */
  public static void main(String[] args) throws ParseException, IOException, InterruptedException {
    System.setProperty("java.awt.headless", "true");

    Options options = new Options();

    Option dataDirOption = new Option("dataDir", true, "data directory");
    dataDirOption.setRequired(true);
    dataDirOption.setType(String.class);
    options.addOption(dataDirOption);

    Option junctionPointsDirOption = JpHelper.createJunctionPointsOption();
    options.addOption(junctionPointsDirOption);
    Option contactPointAndSlopeDirOption = ContactPointAndSlopeHelper.createContactPointAndSlopeDirOption();
    options.addOption(contactPointAndSlopeDirOption);
    Option peakSlopesDirOption = PeakSlopesHelper.createPeakSlopesDirOption();
    options.addOption(peakSlopesDirOption);
    Option svgDefScaleOption = PicoUtil.createSvgDefScaleOption();
    options.addOption(svgDefScaleOption);
    
    Option defLsbVsZLsbSlopeOption = PicoUtil.createDefLsbVsZLsbSlopeOption();
    options.addOption(defLsbVsZLsbSlopeOption);

    AnalysisOptions analysisOptions = AnalysisOptions.create();
    analysisOptions.addTo(options);
    
    Option debugPatternOption = PicoUtil.createDebugPatternOption();
    options.addOption(debugPatternOption);

    CommonClRefoldingOptions commonClRefoldingOptions = CommonClRefoldingOptions.create();
    commonClRefoldingOptions.addTo(options);

    Option continuousOption = new Option(
      "continuous", true, "Record the screen location even if already found in user preferences..");
    continuousOption.setRequired(true);
    continuousOption.setType(Boolean.TYPE);
    options.addOption(continuousOption);

    Option wildcardPatternOption = new Option(
      "wildcardPattern", true, "wildcardPattern for data files");
    wildcardPatternOption.setRequired(true);
    wildcardPatternOption.setType(String.class);
    options.addOption(wildcardPatternOption);
    
    Option allowedTimeRelativeErrorOption = AnalysisOptions.createAllowedTimeRelativeErrorOption();
    options.addOption(allowedTimeRelativeErrorOption);

    Parser parser = new GnuParser();
    CommandLine line = parser.parse(options, args);

    String defLsbVsZLsbSlopeString = line.getOptionValue(defLsbVsZLsbSlopeOption.getOpt());
    double slope = StringUtils.isEmpty(defLsbVsZLsbSlopeString) ? Double.NaN
      : Double.parseDouble(defLsbVsZLsbSlopeString);
    double svgDefScale = Double.parseDouble(line.getOptionValue(svgDefScaleOption.getOpt()));
    Function<? super Double, Double> minPeakForceN = analysisOptions.minPeakForceN(line);
    double minPeakContourLengthM = analysisOptions.minPeakContourLengthM(line);
    double minContourLengthDeltaM = analysisOptions.minContourLengthDeltaM(line);
    double minPeakForceRelativeToEmpiricalBaselineN = analysisOptions.minPeakForceRelativeToEmpiricalBaselineN(line);
    Function<? super Double, Double> medianAbsoluteDeviationLowerBoundN = analysisOptions.medianAbsoluteDeviationLowerBoundN(line);
    double leftMostlyHigherWindowSizeM = analysisOptions.leftMostlyHigherWindowSizeM(line);
    double leftMostlyHigherFraction = analysisOptions.leftMostlyHigherFraction(line);
    double leftMostlyHigherN = analysisOptions.leftMostlyHigherN(line);
    Function<? super Double, Double> rightMuchHigherThanAllN = analysisOptions.rightMuchHigherThanAllN(line);
    Function<? super Double, Double> rightMuchHigherN = analysisOptions.rightMuchHigherN(line);
    double atMostOnePeakWindowM = analysisOptions.atMostOnePeakWindowM(line);
    double temperatureK = analysisOptions.getTemperatureK(line);
    double persistenceLengthM = analysisOptions.persistenceLengthM(line);
    int maxRequiredWellSpacedAtEnd = analysisOptions.getMaxRequiredWellSpacedAtEnd(line);
    double linkerM = analysisOptions.linkerM(line);
    double linkerPerModuleM = analysisOptions.linkerPerModuleM(line);
    double clRangeStartM = commonClRefoldingOptions.clRangeStartM(line);
    double clRangeEndM = commonClRefoldingOptions.clRangeEndM(line);
    boolean continuous = Boolean.parseBoolean(line.getOptionValue(continuousOption.getOpt()));
    String wildcardPattern = line.getOptionValue(wildcardPatternOption.getOpt());
    String debugPattern = line.getOptionValue(debugPatternOption.getOpt());
    // params is only needed if junction points file cannot be found
    FetchRefoldBreakParams params = analysisOptions.fetchRefoldBreakParams(line);
    DoubleList allowedTimeRelativeError = AnalysisOptions.allowedTimeRelativeError(line, allowedTimeRelativeErrorOption);

    Path junctionPointsDir = JpHelper.createJpDirIfNeeded(junctionPointsDirOption, line);
    Path contactPointAndSlopeDir = ContactPointAndSlopeHelper.createCpsDirIfNeeded(
      contactPointAndSlopeDirOption, line);
    String peakSlopesDirString = line.getOptionValue(peakSlopesDirOption.getOpt());
    Path peakSlopesDir = Paths.get(peakSlopesDirString);
    CommonThings.createDirectoryPathIfNeeded(peakSlopesDir);

    Path svgDir = analysisOptions.svgDir(line);
    if (svgDir != null) {
      CommonThings.createDirectoryPathIfNeeded(svgDir);
    }

    Path infoDir = analysisOptions.infoDir(line);
    if (infoDir != null) {
      CommonThings.createDirectoryPathIfNeeded(infoDir);
    }
    double kbtOverP = Wlc.KB * temperatureK / persistenceLengthM;
    Path dataDir = Paths.get(line.getOptionValue(dataDirOption.getOpt()));
    PathMatcher debugPathMatcher = dataDir.getFileSystem().getPathMatcher(debugPattern);
    Set<Path> oldSet = Sets.newHashSet();
    while (true) {
      try (DirectoryStream<Path> stream = Files.newDirectoryStream(dataDir, wildcardPattern)) {
        for (Path file : stream) {
          boolean justAdded = oldSet.add(file);
          if (justAdded) {
            final byte[] fileContent;
            if (continuous) {
              long sleepMillis = 100;
              fileContent = CommonThings.readToByteArrayWithRetryOrNull(
                file, (sleepMillis + 1) / 2, 10, Sleeper.SYSTEM_SLEEPER);
            } else {
              fileContent = Files.readAllBytes(file);
            }
            FileContentAndTargetFile fileContentAndTargetFile = new FileContentAndTargetFile(
              fileContent, file);
            StripChartRefolding.process(
              junctionPointsDir, slope, contactPointAndSlopeDir, peakSlopesDir, svgDefScale, svgDir,
              infoDir, params, allowedTimeRelativeError, minPeakForceN, minPeakContourLengthM, minContourLengthDeltaM,
              minPeakForceRelativeToEmpiricalBaselineN, medianAbsoluteDeviationLowerBoundN,
              leftMostlyHigherWindowSizeM, leftMostlyHigherFraction, leftMostlyHigherN,
              rightMuchHigherThanAllN, rightMuchHigherN,
              atMostOnePeakWindowM, linkerM, linkerPerModuleM, maxRequiredWellSpacedAtEnd,
              clRangeStartM, clRangeEndM, kbtOverP, false,
              debugPathMatcher,
              fileContentAndTargetFile, CommonThings.callerRunsExecutor);
          }
        }
      }
      if (continuous) {
        Thread.sleep(5000);
      } else {
        break;
      }
    }
  }
}
